#Region "Imports"
Imports System.IO
#End Region
Friend Class clsPDFBaseReader
#Region "Private Members"
    Private _buffer() As Byte
    Private _readerPosition As Integer
    Private _eof As Boolean
    Private _orientation As ReadOrientations
    Private _endobjPattern() As Byte
    Private _streamPattern() As Byte
    Private _endstreamPattern() As Byte
    Private _startxrefPattern() As Byte
    Private _true() As Byte
    Private _false() As Byte
    Private Const _blockToRead As Integer = 8192
#End Region
#Region "Constructors"
    Public Sub New(ByVal fichierPath As String)
        Dim reader As New BinaryReader(File.Open(fichierPath, FileMode.Open, FileAccess.Read))
        _buffer = reader.ReadBytes(CInt(reader.BaseStream.Length))
        reader.Close()
        _init()
    End Sub
    Public Sub New(ByVal buffer() As Byte)
        _buffer = buffer
        _init()
    End Sub
#End Region
#Region "Public Properties"
    Public ReadOnly Property Eof() As Boolean
        Get
            Eof = _eof
        End Get
    End Property
    Public ReadOnly Property Length() As Integer
        Get
            Length = _buffer.Length
        End Get
    End Property
    Public Property ReaderPosition() As Integer
        Get
            ReaderPosition = _readerPosition
        End Get
        Set(ByVal value As Integer)
            _readerPosition = value
            If ReaderPosition > Length Then
                _eof = True
            Else
                _eof = False
            End If
        End Set
    End Property
    Public ReadOnly Property Buffer As Byte()
        Get
            Buffer = _buffer
        End Get
    End Property
#End Region
#Region "Public methods"
    Public Function GetObject() As clsPdfBaseObject
        Dim b As Byte
        GetObject = Nothing
        b = SkipWhiteSpace()
        If Not Eof Then
            GetObject = New clsPdfBaseObject
            GetObject.Position = ReaderPosition - 1
            Select Case b
                Case 37 ' %
                    _getCommentObject(GetObject)
                Case 40 ' (
                    _getLiteralString(GetObject)
                Case 47 '/
                    _getNameObject(GetObject)
                Case 91 '[
                    _getArrayObject(GetObject)
                Case 60 '<
                    _getHexStringOrDictionnaryObject(GetObject)
                Case 48 To 57, 45, 43
                    GetObject.AddValue(b)
                    _getNumericObjectOrIndirectReferenceOrIndirectObject(GetObject)
                Case 115 's
                    _getStreamObject(GetObject)
                Case 116 't
                    _getBooleanTrueObject(GetObject)
                Case 102 'f
                    _getBooleanFalseObject(GetObject)
            End Select
        End If
    End Function
    Public Function GetWord() As String
        Dim b As Byte
        Dim word As String
        word = ""

        b = SkipWhiteSpace()
        If Not Eof Then
            word += Convert.ToChar(b)
            b = GetByte()
            While Not Eof And Not _isPdfWhiteSpace(b) And Not _isPdfDelimiter(b)
                word += Convert.ToChar(b)
                b = GetByte()
            End While
            If _isPdfDelimiter(b) Then
                ReaderPosition -= 1
            End If
        End If
        GetWord = word
    End Function
#End Region
#Region "Friend methods"
    Friend Function GetByte() As Byte
        If ReaderPosition = Length Then
            _eof = True
        Else
            GetByte = _buffer(ReaderPosition)
            ReaderPosition += 1
        End If
    End Function
    Friend Function GetByte(buffer As Byte(), count As Integer) As Integer
        GetByte = 0
        If ReaderPosition = Length Then
            _eof = True
        Else
            Dim toRead As Integer
            If _buffer.Length - ReaderPosition < count Then
                toRead = CInt(_buffer.Length - ReaderPosition)
            Else
                toRead = count
            End If
            Array.Copy(_buffer, ReaderPosition, buffer, 0, toRead)
            ReaderPosition += toRead
            GetByte = toRead
        End If

    End Function
    Friend Function _getStartXrefPosition() As Integer
        Dim spos, epos As Integer
        Dim ret, j, patternIndex As Integer
        Dim found As Boolean

        Dim buffer(_blockToRead) As Byte

        ReaderPosition = Length - 1

        found = False
        If _blockToRead >= Length Then
            ReaderPosition = 0
        Else
            ReaderPosition -= _blockToRead
        End If
        spos = ReaderPosition
        epos = spos
        ret = GetByte(buffer, _blockToRead + 1)
        patternIndex = 0
        While ret > 0 And Not found
            j = _findBytePattern(buffer, _startxrefPattern, ret, patternIndex, 0)
            If j < 0 Then
                epos += ret
                ReaderPosition -= epos - _blockToRead
                ret = GetByte(buffer, _blockToRead)
            Else
                epos += (j + 1)
                found = True
            End If
        End While
        _getStartXrefPosition = epos

    End Function
#End Region
#Region "Private methods"
    Private Sub _getBooleanFalseObject(ByVal pdfObject As clsPdfBaseObject)
        Dim b As Byte
        Dim i As Integer
        i = 1
        b = GetByte()
        While Not Eof
            If b <> _false(i) Then
                'This is not a boolean false object
                Exit Sub
            End If
            i += 1
            If i = _false.Length Then
                Exit While
            End If
            b = GetByte()
        End While

        'We have a boolean false object
        pdfObject.Type = PdfObjectTypes.BooleanObject
        pdfObject.SubType = PdfObjectSubTypes.FalseObject
    End Sub
    Private Sub _getBooleanTrueObject(ByVal pdfObject As clsPdfBaseObject)
        Dim b As Byte
        Dim i As Integer
        i = 1
        b = GetByte()
        While Not Eof
            If b <> _true(i) Then
                'This is not a boolean true object
                Exit Sub
            End If
            i += 1
            If i = _true.Length Then
                Exit While
            End If
            b = GetByte()
        End While

        'We have a boolean true object
        pdfObject.Type = PdfObjectTypes.BooleanObject
        pdfObject.SubType = PdfObjectSubTypes.TrueObject
    End Sub
    Private Sub _getStreamObject(ByRef pdfObject As clsPdfBaseObject)
        Dim obj As New clsPdfBaseObject
        Dim b As Byte
        Dim i As Integer

        'We skip the beginning of the stream object
        _readerPosition -= 1
        _getObjectByPattern(obj, _streamPattern)

        'We have a stream object
        pdfObject.Type = PdfObjectTypes.StreamObject
        pdfObject.ClearValue()

        'We skip any end of line marker
        b = GetByte()
        While b = 10 Or b = 13
            b = GetByte()
        End While
        ReaderPosition -= 1
        _getObjectByPattern(pdfObject, _endstreamPattern)

        'We have to remove any end of line marker
        For i = pdfObject.Value.Length - 1 To 0 Step -1
            If pdfObject.Value(i) <> 10 And pdfObject.Value(i) <> 13 Then
                ReDim Preserve pdfObject.Value(i)
                Exit For
            End If
        Next
    End Sub

    Private Sub _getIndirectObject(ByRef pdfObject As clsPdfBaseObject)
        pdfObject.Type = PdfObjectTypes.IndirectObject
        pdfObject.ClearValue()
        _getObjectByPattern(pdfObject, _endobjPattern)
    End Sub
    Private Sub _getObjectByPattern(ByRef pdfObject As clsPdfBaseObject, pattern As Byte())
        Dim spos As Integer
        Dim j, size, patternIndex As Integer
        Dim block As Byte()

        spos = ReaderPosition

        j = _findBytePattern(Buffer, pattern, Buffer.Length, patternIndex, spos)


        If j > 0 Then
            ReaderPosition = spos
            j -= pattern.Length
            size = CInt(j - spos + 1)
            ReDim block(size)
            GetByte(block, size)
            pdfObject.SetValue(block)
            ReaderPosition = j + pattern.Length + 1
        Else
            Throw New Exception("_getObjectByPattern not found")
        End If


    End Sub
    Private Function _findBytePattern(source As Byte(), pattern As Byte(), sizeSource As Integer, ByRef patternIndex As Integer, start As Integer) As Integer
        Dim j As Integer
        _findBytePattern = -1
        j = start
        While j < sizeSource
            If source(j) = pattern(patternIndex) Then
                While source(j) = pattern(patternIndex) And patternIndex < (pattern.Length - 1)
                    patternIndex += 1
                    j += 1
                    If j = sizeSource Then
                        Exit While
                    End If
                End While
                If patternIndex = pattern.Length - 1 Then
                    _findBytePattern = j
                    Exit While
                End If
                If j < sizeSource Then
                    patternIndex = 0
                End If
            Else
                patternIndex = 0
                j += 1
            End If
        End While

    End Function
    Private Sub _getNumericObjectOrIndirectReferenceOrIndirectObject(ByRef pdfObject As clsPdfBaseObject)
        Dim position As Integer
        Dim generation As String
        Dim reference As String

        _getNumericObject(pdfObject)
        position = ReaderPosition

        generation = GetWord()
        reference = GetWord()
        If reference = PDF_REFERENCE Then
            pdfObject.Type = PdfObjectTypes.IndirectReferenceObject
            pdfObject.ObjectNumber = pdfObject.GetLong
            pdfObject.GenerationNumber = Convert.ToInt64(generation)
        Else
            If reference = PDF_OBJ Then
                pdfObject.ObjectNumber = pdfObject.GetLong
                pdfObject.GenerationNumber = Convert.ToInt64(generation)
                _getIndirectObject(pdfObject)
            Else
                ReaderPosition = position
            End If
        End If
    End Sub
    Private Sub _getCommentObject(ByRef pdfObject As clsPdfBaseObject)
        Dim b As Byte

        pdfObject.Type = PdfObjectTypes.CommentObject
        b = GetByte()
        While Not Eof And Not _isPdfWhiteSpace(b)
            pdfObject.AddValue(b)
            b = GetByte()
        End While
    End Sub
    Private Sub _getNumericObject(ByRef pdfObject As clsPdfBaseObject)
        Dim b As Byte

        pdfObject.Type = PdfObjectTypes.NumericObject
        b = GetByte()
        While Not Eof
            Select Case b
                Case 48 To 57, 45, 43, 46
                    pdfObject.AddValue(b)
                Case Else
                    ReaderPosition -= 1
                    Exit While
            End Select
            b = GetByte()
        End While
    End Sub
    Private Sub _getDictionnaryObject(ByRef pdfObject As clsPdfBaseObject)
        Dim b As Byte
        Dim openCount As Integer = 1

        pdfObject.Type = PdfObjectTypes.DictionaryObject
        While openCount > 0 And Not Eof
            b = GetByte()
            If b = 60 And _peekByte() = 60 Then
                pdfObject.AddValue(b)
                pdfObject.AddValue(b)
                openCount += 1
                ReaderPosition += 1
            Else
                If b = 62 And _peekByte() = 62 Then
                    openCount -= 1
                    ReaderPosition += 1
                    If openCount > 0 Then
                        pdfObject.AddValue(b)
                        pdfObject.AddValue(b)
                    End If
                Else
                    If openCount > 0 Then
                        pdfObject.AddValue(b)
                    End If
                End If
            End If
        End While

    End Sub
    Private Sub _getHexStringOrDictionnaryObject(ByRef pdfObject As clsPdfBaseObject)
        Dim b As Byte

        b = GetByte()
        If Not Eof Then
            If b = 60 Then '< then
                _getDictionnaryObject(pdfObject)
            Else
                pdfObject.Type = PdfObjectTypes.StringObject
                pdfObject.SubType = PdfObjectSubTypes.HexadecimalStringObject
                While Not Eof And b <> 62 '>
                    pdfObject.AddValue(b)
                    b = GetByte()
                End While
            End If
        End If
    End Sub
    Private Sub _getArrayObject(ByRef pdfObject As clsPdfBaseObject)
        Dim openCount As Integer = 1

        pdfObject.Type = PdfObjectTypes.ArrayObject
        _readToEndOfToken(0, 93, pdfObject)

    End Sub
    Private Sub _readToEndOfToken(level As Integer, token As Byte, ByRef pdfObject As clsPdfBaseObject)
        Dim b, p As Byte

        b = GetByte()
        While Not Eof
            Select Case b
                Case 40 '(
                    pdfObject.AddValue(b)
                    _readToEndOfToken(level + 1, 41, pdfObject)
                Case 91 '[
                    pdfObject.AddValue(b)
                    If token <> 41 Then 'A string cannot contain an array
                        _readToEndOfToken(level + 1, 93, pdfObject)
                    End If
                Case 60 '<
                    pdfObject.AddValue(b)
                    If token = 93 Then
                        'An array can contain a dictionnary
                        _readToEndOfToken(level + 1, 62, pdfObject)
                    End If
                Case 92 '\
                    pdfObject.AddValue(b)
                    If token = 41 Then
                        p = _peekByte()
                        If p = 40 Or p = 41 Then
                            b = GetByte()
                            pdfObject.AddValue(b)
                        End If
                    End If
                Case token
                    If level > 0 Then
                        pdfObject.AddValue(b)
                    End If
                    Exit While
                Case Else
                    pdfObject.AddValue(b)
            End Select
            b = GetByte()
        End While
    End Sub
    Private Sub _getNameObject(ByRef pdfObject As clsPdfBaseObject)
        Dim b As Byte

        pdfObject.Type = PdfObjectTypes.NameObject
        b = GetByte()
        While Not Eof And Not _isPdfWhiteSpace(b) And Not _isPdfDelimiter(b)
            pdfObject.AddValue(b)
            b = GetByte()
        End While
        If _isPdfDelimiter(b) Then
            ReaderPosition -= 1
        End If
    End Sub
    Private Sub _getLiteralString(ByRef pdfObject As clsPdfBaseObject)
        Dim b As Byte

        Dim openParenthesisCount As Integer = 1

        pdfObject.Type = PdfObjectTypes.StringObject
        pdfObject.SubType = PdfObjectSubTypes.LiteralStringObject

        While openParenthesisCount > 0 And Not Eof
            b = GetByte()
            If b = 92 Then '\
                'Backslash must be treated specially
                b = GetByte()
                Select Case b
                    Case 40 '(
                        pdfObject.AddValue(b)
                    Case 41 ')
                        pdfObject.AddValue(b)
                    Case Else
                        pdfObject.AddValue(92)
                        pdfObject.AddValue(b)
                End Select
                b = GetByte()
            End If
            If b = 40 Then '(
                openParenthesisCount += 1
                pdfObject.AddValue(b)
            Else
                If b = 41 Then ')
                    openParenthesisCount -= 1
                    If openParenthesisCount > 0 Then
                        pdfObject.AddValue(b)
                    End If
                Else
                    pdfObject.AddValue(b)
                End If
            End If
        End While
    End Sub
    Private Function _isPdfWhiteSpace(ByVal b As Byte) As Boolean
        If b = 0 Or b = 9 Or b = 10 Or b = 12 Or b = 13 Or b = 32 Then
            _isPdfWhiteSpace = True
        Else
            _isPdfWhiteSpace = False
        End If
    End Function
    Private Function _isPdfDelimiter(ByVal b As Byte) As Boolean
        If b = 40 Or b = 41 Or b = 60 Or b = 62 Or b = 91 Or b = 93 Or b = 123 Or b = 125 Or b = 47 Or b = 37 Then
            _isPdfDelimiter = True
        Else
            _isPdfDelimiter = False
        End If
    End Function
    Private Function _peekByte() As Byte
        _peekByte = GetByte()
        If Not Eof Then
            ReaderPosition -= 1
        End If
    End Function
    Private Function SkipWhiteSpace() As Byte
        Dim b As Byte

        b = GetByte()
        While Not Eof And _isPdfWhiteSpace(b)
            b = GetByte()
        End While
        SkipWhiteSpace = b
    End Function
    Private Sub _init()

        _orientation = ReadOrientations.Forward
        ReaderPosition = 0
        _eof = False

        _stringToByteArray(PDF_ENDOBJ, _endobjPattern)
        _stringToByteArray(PDF_STREAM, _streamPattern)
        _stringToByteArray(PDF_ENDSTREAM, _endstreamPattern)
        _stringToByteArray(PDF_STARTXREF, _startxrefPattern)
        _stringToByteArray(PDF_TRUE, _true)
        _stringToByteArray(PDF_FALSE, _false)

    End Sub
    Private Sub _stringToByteArray(ByVal s As String, ByRef b() As Byte)
        Dim c As Char
        Dim i As Integer
        ReDim b(s.Length - 1)
        i = 0
        For Each c In s
            b(i) = Convert.ToByte(c)
            i += 1
        Next
    End Sub

#End Region
End Class
